package me.lwn.auth.security.core.userdetails;

import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.*;

public class User implements UserDetails, CredentialsContainer {

    private String username;
    private String password;
    private String name;
    private String mobile;
    private String email;
    private String headImage;
    private Date accountExpireDate;
    private Date credentialExpireDate;
    private boolean accountLocked;
    private boolean enabled;
    private Set<GrantedAuthority> authorities;

    public User(String username, String password, String name, String mobile, String email, String headImage,
                Date accountExpireDate, Date credentialExpireDate, boolean accountLocked, boolean enabled,
                Collection<? extends GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.name = name;
        this.mobile = mobile;
        this.email = email;
        this.headImage = headImage;
        this.accountExpireDate = accountExpireDate;
        this.credentialExpireDate = credentialExpireDate;
        this.accountLocked = accountLocked;
        this.enabled = enabled;
        this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    }

    private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
        Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
        // Ensure array iteration order is predictable (as per
        // UserDetails.getAuthorities() contract and SEC-717)
        SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(new AuthorityComparator());
        for (GrantedAuthority grantedAuthority : authorities) {
            Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
            sortedAuthorities.add(grantedAuthority);
        }
        return sortedAuthorities;
    }

    public String getMobile() {
        return mobile;
    }

    public Date getAccountExpireDate() {
        return accountExpireDate;
    }

    public Date getCredentialExpireDate() {
        return credentialExpireDate;
    }

    public boolean isAccountLocked() {
        return accountLocked;
    }

    @Override
    public void eraseCredentials() {
        this.password = null;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public String getHeadImage() {
        return headImage;
    }

    @Override
    public boolean isAccountNonExpired() {
        if (this.accountExpireDate == null) {
            return true;
        } else {
            return new Date().after(this.accountExpireDate);
        }
    }

    @Override
    public boolean isAccountNonLocked() {
        return !this.accountLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        if (this.credentialExpireDate == null) {
            return true;
        } else {
            return new Date().after(this.credentialExpireDate);
        }
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }

    private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {

        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

        @Override
        public int compare(GrantedAuthority g1, GrantedAuthority g2) {
            // Neither should ever be null as each entry is checked before adding it to
            // the set. If the authority is null, it is a custom authority and should
            // precede others.
            if (g2.getAuthority() == null) {
                return -1;
            }
            if (g1.getAuthority() == null) {
                return 1;
            }
            return g1.getAuthority().compareTo(g2.getAuthority());
        }

    }
}
